#include <ctime>
#include <queue>
#include <map>
#include <set>
#include <list>
#include <iostream>
#include <cstring>
#include "process.cpp"
#include "scheduler.h"
#include "limits.h"

using namespace std;

Scheduler::Scheduler(int ipolicy) {
  srand(time(NULL));

  scheduling_policy = ipolicy;
  RR_Time = 0;
  L_Tickets = 0;
  currentProcess = NULL;

  if(scheduling_policy == 0){
     cout << "Starting Scheduler: Scheduling Policy = Round-Robin" << endl;
  }
  else if(scheduling_policy == 1){
     cout << "Starting Scheduler: Scheduling Policy = Lottery" << endl;
  }
  else{
     cout << "Invalid Policy Selection: Using Round-Robin" << endl;
     scheduling_policy = 0;
  }
}

// choose scheduling method based on mode bit
SchedulerReturn Scheduler::run(int tick) {
  if(scheduling_policy == 0){
    return runRR(tick);
  }
  else{
    return runL(tick);
  }
}

// round-robin scheduler 
SchedulerReturn Scheduler::runRR(int tick) {
  SchedulerReturn sr;
  ProcessReturn pr;
  sr.return_code = SRET_ERROR;
  pr.ret = RET_SUCCESS;
  
 // currently not running process and no active or waiting processes
  if(currentProcess == NULL && ActiveProcesses.size() == 0 && WaitingProcesses.size() == 0){
    sr.return_code = SRET_NO_PROCESSES;
  }

  // currently not running process and no active processes
  else if(currentProcess == NULL && ActiveProcesses.size() == 0){
    sr.return_code = SRET_NO_ACTIVE_PROCESSES;
  }	
  
  // there are processes available to run
  else{
    
    // if no process currently running and active process available, load next active process in queue
    if(currentProcess == NULL && ActiveProcesses.size() > 0){
      currentProcess = ActiveProcesses.find(ActiveQueue.front())->second;
      currentPid = ActiveQueue.front();
      ActiveQueue.pop();
      RR_Time = TIME_SLICE;
      cout << "Loading next item in queue: pid = " << currentPid << endl;
    }
    
    // if currently running process used entire time slice, switch for next process in queue
    if(currentProcess != NULL && RR_Time <=0){
      cout << "Switching Processes: Old Pid = " << currentPid;
      switchProcesses();
      cout << ", New Pid = " << currentPid << endl;
    } 
    
    // run process
    pr = currentProcess->run();

    // uneventful run
    if(pr.ret == RET_SUCCESS){
      RR_Time--;
      sr.return_code = SRET_SUCCESS;
      sr.priority = currentProcess->returnPriority();
      sr.pid = currentPid;
      strcpy(sr.file_name, currentProcess->returnCodeLoc());
    }

    // process terminated
    else if(pr.ret == RET_TERMINATED){
      sr.priority = currentProcess->returnPriority();
      sr.pid = currentPid;
      strcpy(sr.file_name, currentProcess->returnCodeLoc());
      ActiveProcesses.erase(currentPid);
      currentProcess->close();
      delete currentProcess;
      currentProcess = NULL;
      pidSet.erase(currentPid);
      RR_Time = 0;
      sr.return_code = SRET_SUCCESS;
    }

    // notify OS of new process to create
    else if(pr.ret == RET_NEW_PROCESS){
      sr.vals.newProcess.priority = pr.vals.newProcess.priority;
      tempPid = getPid();
      if(tempPid == -1){
        cout << "All Pid Values Occupied" << endl;
      }
      sr.vals.newProcess.pid = tempPid;
      strcpy(sr.vals.newProcess.file_name, pr.vals.newProcess.file_name);
      RR_Time--;
      sr.priority = currentProcess->returnPriority();
      sr.pid = currentPid;
      strcpy(sr.file_name, currentProcess->returnCodeLoc());
      sr.return_code = SRET_NEW_PROCESS;
    }

    // notify OS of new system call
    else if(pr.ret == RET_SYSCALL){
      WaitingProcesses.insert(pair<int,Process*>(currentPid,currentProcess));
      ActiveProcesses.erase(currentPid);
      sr.vals.sysCall.pid = currentProcess->returnPid();
      strcpy(sr.vals.sysCall.bc, pr.vals.bc);
      RR_Time = 0;
      sr.priority = currentProcess->returnPriority();
      sr.pid = currentPid;
      strcpy(sr.file_name, currentProcess->returnCodeLoc());
      currentProcess = NULL;
      sr.return_code = SRET_SYSCALL;
      cout << "Adding process " << currentPid << " to blocked process list" << endl;
    }

    // process indicates error
    else{
      cout << "Something went wrong in process" << endl; 
    }
  }
  return sr;
}


// implement lottery scheduling here
SchedulerReturn Scheduler::runL(int tick) {
  SchedulerReturn sr;
  ProcessReturn pr;
  sr.return_code = SRET_ERROR;
  pr.ret = RET_SUCCESS;
  
 // currently not running process and no active or waiting processes
  if(ActiveProcesses.size() == 0 && WaitingProcesses.size() == 0){
    sr.return_code = SRET_NO_PROCESSES;
  }

  // currently not running process and no active processes
  else if(ActiveProcesses.size() == 0){
    sr.return_code = SRET_NO_ACTIVE_PROCESSES;
  }	
  
  // there are processes available to run
  else{

    // Hide old process
    if (currentProcess != NULL) {
      currentProcess = NULL;
    }

    // Lottery for new process
    int ticket = rand() % L_Tickets + 1;
    cout << "Chosen ticket: " << ticket << endl;

    int total = 0;
    // Bad coding, I know...
    for (map<int,Process*>::iterator i = ActiveProcesses.begin(); i != ActiveProcesses.end(); ++i) {
      currentProcess = i->second;
      total += 11 - i->second->returnPriority();
      if (total >= ticket) {
	break;
      }
    }

    if (currentProcess == NULL) {
      cout << "Null cp!" << endl;
    }

    currentPid = currentProcess->returnPid();

    // run process
    pr = currentProcess->run();
    sr.start_time  = pr.start_time;
    sr.cycles_completed = pr.cycles_completed;

    // uneventful run
    if(pr.ret == RET_SUCCESS){
      sr.return_code = SRET_SUCCESS;
      sr.priority = currentProcess->returnPriority();
      sr.pid = currentPid;
      strcpy(sr.file_name, currentProcess->returnCodeLoc());
    }

    // process terminated
    else if(pr.ret == RET_TERMINATED){
      sr.priority = currentProcess->returnPriority();
      sr.pid = currentPid;
      strcpy(sr.file_name, currentProcess->returnCodeLoc());
      L_Tickets -= 11 - currentProcess->returnPriority();
      cout << "  Total Tickets:" << L_Tickets << endl;
      ActiveProcesses.erase(currentPid);
      currentProcess->close();
      delete currentProcess;
      currentProcess = NULL;
      pidSet.erase(currentPid);
      sr.return_code = SRET_SUCCESS;
    }

    // notify OS of new process to create
    else if(pr.ret == RET_NEW_PROCESS){
      sr.vals.newProcess.priority = pr.vals.newProcess.priority;
      tempPid = getPid();
      if(tempPid == -1){
        cout << "All Pid Values Occupied" << endl;
      }
      sr.vals.newProcess.pid = tempPid;
      strcpy(sr.vals.newProcess.file_name, pr.vals.newProcess.file_name);
      sr.return_code = SRET_NEW_PROCESS;
      sr.priority = currentProcess->returnPriority();
      sr.pid = currentPid;
      strcpy(sr.file_name, currentProcess->returnCodeLoc());
    }

    // notify OS of new system call
    else if(pr.ret == RET_SYSCALL){
      L_Tickets -= 11 - currentProcess->returnPriority();
      cout << "  Total Tickets:" << L_Tickets << endl;
      WaitingProcesses.insert(pair<int,Process*>(currentPid,currentProcess));
      ActiveProcesses.erase(currentPid);
      sr.vals.sysCall.pid = currentProcess->returnPid();
      strcpy(sr.vals.sysCall.bc, pr.vals.bc);
      sr.priority = currentProcess->returnPriority();
      sr.pid = currentPid;
      strcpy(sr.file_name, currentProcess->returnCodeLoc());
      currentProcess = NULL;
      sr.return_code = SRET_SYSCALL;
      cout << "Adding process " << currentPid << " to blocked process list" << endl;
    }

    // process indicates error
    else{
      cout << "Something went wrong in process" << endl; 
    }
  }
  return sr;
}


// push current process to back of active process queue and load process at front of queue
int Scheduler::switchProcesses() {
  
  ActiveQueue.push(currentPid);
  currentProcess = ActiveProcesses.find(ActiveQueue.front())->second;
  currentPid = ActiveQueue.front();
  ActiveQueue.pop();
  RR_Time = TIME_SLICE;

  return 0;
}

// allow OS to add processes when created
int Scheduler::AddProcess(int ipid, Process* p) {
  if (scheduling_policy == ROUND_ROBIN) {
    ActiveQueue.push(ipid);
  } else { // scheduling_policy != ROUND_ROBIN
    cout << "Adding " << 11 - p->returnPriority() << " tickets for PID = " << ipid << endl;
    L_Tickets += 11 - p->returnPriority();
    cout << "  Total Tickets:" << L_Tickets << endl;
  }

  ActiveProcesses.insert(pair<int,Process*>(ipid,p));
}

// allow OS to indicate system call completeion and to unblock process.
int Scheduler::UnblockProcess(int ipid) {
  cout << "Unblocking process: " << ipid << endl;
  Process* unblocked = WaitingProcesses.find(ipid)->second;
  //  ActiveProcesses.insert(pair<int,Process*>(ipid,WaitingProcesses.find(ipid)->second)); 
  ActiveProcesses.insert(pair<int,Process*>(ipid,unblocked)); 
  WaitingProcesses.erase(ipid);
  if (scheduling_policy == ROUND_ROBIN) {
    ActiveQueue.push(ipid);
  } else {
    L_Tickets += 11 - unblocked->returnPriority();
    cout << "  Total tickets: " << L_Tickets << endl;
  }
}

// select lowest available pid
int Scheduler::getPid() {

  int i;
  int ret_value = -1;

  for(i = 1; i < INT_MAX; i++){
    if((pidSet.insert(i)).second){
       ret_value = i;
       break;
    }
  }
  return ret_value;
}
